/**
 * @author zhouheng
 * @date 2022年03月26日 20:38
 */

package com.atguigu.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.constant.RabbitInfo;
import com.atguigu.common.to.SeckillSkuRedisTo;
import com.atguigu.common.to.mq.SecKillOrderTo;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.MemberRespVo;
import com.atguigu.common.vo.SkuHasStockVo;
import com.atguigu.common.vo.SkuInfoVo;
import com.atguigu.seckill.feign.CouponFeignService;
import com.atguigu.seckill.feign.ProductFeignService;
import com.atguigu.seckill.interceptor.LoginUserInterceptor;
import com.atguigu.seckill.service.SeckillService;
import com.atguigu.seckill.vo.SeckillSessionsWithSkus;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class SeckillServiceImpl implements SeckillService {

    public static final String SESSION_CACHE_PREFIX = "seckill:sessions:";
    public static final String SKUKILL_CACHE_PREFIX = "seckill:skus:";
    private final String SKUSTOCK_SEMAPHONE = "seckill:stock:"; // +商品随机码

    @Autowired
    CouponFeignService couponFeignService;

    @Autowired
    ProductFeignService productFeignService;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public void uploadSeckillSkuLatest3Day() {
        // 1.扫描最近三天要参加秒杀的商品
        R r = couponFeignService.getLate3DaySession();

        if (r.getCode() == 0) {
            List<SeckillSessionsWithSkus> sessions = r.getData(new TypeReference<List<SeckillSessionsWithSkus>>() {});
            // 2.缓存活动信息
            saveSessionInfo(sessions);
            // 3.缓存活动的关联的商品信息
            saveSessionSkuInfo(sessions);
        }
    }

    @Override
    public List<SeckillSkuRedisTo> getCurrentSeckillSkus() {
        // 1.确定当前时间属于那个秒杀场次
        long time = new Date().getTime();

        Set<String> keys = stringRedisTemplate.keys(SESSION_CACHE_PREFIX + "*");
        keys.stream().forEach(key -> {
            String[] split = key.split("_");
            long start = Long.parseLong(split[0]);
            long end = Long.parseLong(split[1]);

            if (start <= time && end >= time) {
                List<String> skus = stringRedisTemplate.opsForList().range(key, 0, 100);

                BoundHashOperations<String, String, String> ops = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                List<String> skuinfos = ops.multiGet(skus);
                List<SeckillSkuRedisTo> seckillSkuRedisTos = skuinfos.stream().map(skuinfo -> {
                    SeckillSkuRedisTo redisTo = JSON.parseObject(skuinfo, SeckillSkuRedisTo.class);
                    return redisTo;
                }).collect(Collectors.toList());
            }
        });

        return null;
    }

    @Override
    public SeckillSkuRedisTo getSkuSeckillInfo(Long skuId) {
        BoundHashOperations<String, String, String> ops = stringRedisTemplate
            .boundHashOps(SKUKILL_CACHE_PREFIX);
        Set<String> keys = ops.keys();
        if(keys != null && keys.size() > 0){
            for (String key : keys) {
                // 匹配sessionId-skuId
                String regx = "\\d-" + skuId;
                if (Pattern.matches(regx, key)) {
                    String json = ops.get(key);
                    SeckillSkuRedisTo to = JSON.parseObject(json, SeckillSkuRedisTo.class);
                    // 如果没开始秒杀，就清除随机码
                    long current = new Date().getTime();
                    if(current <= to.getStartTime() || current >= to.getEndTime()){
                        to.setRandomCode(null);
                    }
                    return to;
                }
            }
        }

        return null;
    }

    // killId 场次id
    // key 随机码
    // num 秒杀数量
    @Override
    public String kill(String killId, String key, Integer num) {
        MemberRespVo memberRespVo = LoginUserInterceptor.threadLocal.get();

        BoundHashOperations<String, String, String> hashOps = stringRedisTemplate
            .boundHashOps(SKUKILL_CACHE_PREFIX);
        String json = hashOps.get(key);

        if (StringUtils.isBlank(json)) {
            return null;
        }

        SeckillSkuRedisTo redisTo = JSON.parseObject(json, SeckillSkuRedisTo.class);
        // 校验合法性
        long time = new Date().getTime();
        String skuId = redisTo.getPromotionSessionId() + "-" + redisTo.getSkuId();
        String randomCode = redisTo.getRandomCode();
        if(time >= redisTo.getStartTime() && time <= redisTo.getEndTime()){
            if (key.equals(randomCode)  && killId.equals(skuId)) {
                // 2.说明数据合法
                BigDecimal limit = redisTo.getSeckillLimit();
                if(num <= limit.intValue()){
                    // 3. userId+skuId在redis中标识买过商品
                    String redisKey = memberRespVo.getId() + "-" + skuId;

                    // 让数据自动过期
                    long ttl = redisTo.getEndTime() - redisTo.getStartTime();
                    Boolean aBoolean = stringRedisTemplate.opsForValue()
                        .setIfAbsent(redisKey, num.toString(), ttl < 0 ? 0 : ttl, TimeUnit.SECONDS);
                    if (aBoolean) {
                        // 占位成功 说明从来没买过
                        RSemaphore semaphore = redissonClient.getSemaphore(SKUSTOCK_SEMAPHONE + randomCode);
                        boolean acquire = semaphore.tryAcquire(num);
                        // 抢占成功 准备下订单操作
                        if (acquire) {
                            // 秒杀成功// 快速下单 发送MQ
                            /** 生成订单号，这样数据库通过消息队列保存后，订单支付页面也知道保存的id是多少 */
                            String orderSn = IdWorker.getTimeId() + UUID.randomUUID().toString().replace("-","").substring(7,8);
                            SecKillOrderTo orderTo = new SecKillOrderTo();
                            orderTo.setOrderSn(orderSn);
                            orderTo.setMemberId(memberRespVo.getId());
                            orderTo.setNum(num);
                            orderTo.setSkuId(redisTo.getSkuId());
                            orderTo.setSeckillPrice(redisTo.getSeckillPrice());
                            orderTo.setPromotionSessionId(redisTo.getPromotionSessionId());
                            rabbitTemplate.convertAndSend(RabbitInfo.Order.exchange,
                                RabbitInfo.SecKill.delayRoutingKey, orderTo);
                            // 返回订单号
                            return orderSn;
                        }
                    }
                }
            }
        }


        return null;
    }

    // 3.缓存活动的关联的商品信息
    // hashmap存储
    private void saveSessionSkuInfo(List<SeckillSessionsWithSkus> sessions) {
        sessions.stream().forEach(session -> {
            BoundHashOperations<String, Object, Object> ops = stringRedisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
            // 遍历sku往上面redis里放内容
            session.getRelationSkus().stream().forEach(seckillSkuVo -> {
                String key = seckillSkuVo.getPromotionSessionId() + "-" + seckillSkuVo.getSkuId();
                if (!stringRedisTemplate.hasKey(key)) {
                    String randomCode = UUID.randomUUID().toString().replace("-", "");
                    // 2.缓存商品
                    SeckillSkuRedisTo redisTo = new SeckillSkuRedisTo();
                    BeanUtils.copyProperties(seckillSkuVo, redisTo);
                    redisTo.setStartTime(session.getStartTime().getTime());
                    redisTo.setEndTime(session.getEndTime().getTime());
                    // 3.sku的基本数据 sku的秒杀信息
                    R info = productFeignService.skuInfo(seckillSkuVo.getSkuId());
                    if(info.getCode() == 0){
                        SkuInfoVo skuInfo = info.getData("skuInfo", new TypeReference<SkuInfoVo>() {});
                        redisTo.setSkuInfoVo(skuInfo);
                    }
                    // 设置随机码
                    redisTo.setRandomCode(randomCode);

                    ops.put(key, JSON.toJSONString(redisTo));

                    // 5.使用库存作为分布式信号量  限流，得到信号量才去改db
                    RSemaphore semaphore = redissonClient.getSemaphore(SKUSTOCK_SEMAPHONE + randomCode);
                    semaphore.trySetPermits(seckillSkuVo.getSeckillCount().intValue());
                }


            });


        });
    }

    // 2.缓存活动信息
    // 一个活动场次中包含的所有商品信息
    // redis list结构存储
    // sessionid + skuId
    private void saveSessionInfo(List<SeckillSessionsWithSkus> sessions) {
        sessions.stream().forEach(session -> {
            String key = session.getStartTime() + "_" + session.getEndTime();
            // 判断key存在
            if (stringRedisTemplate.hasKey(key)) {
                return;
            }
            List<String> skuIds = session.getRelationSkus().stream().map(t -> {
                String res = t.getPromotionSessionId() + "-" + t.getSkuId();
                return res;
            }).collect(Collectors.toList());

            stringRedisTemplate.opsForList().leftPushAll(key, skuIds);
        });
    }
}
